package bond

import (
	"fmt"
	"path/filepath"
	"strings"

	"gitee.com/wmdng/vake/ninja"
)

type Node interface {
	GetType() string
	GetExport() []string
	RunGenerate(nj *ninja.Ninja) error
}

type NFile struct {
	ext string
	fds []string
	opt *Option
}

/*
c : c lang souce
asm : assembler souce
library : ar, achive
object : elf, relocatable file
program : elf, executable file
*/
func (nf *NFile) GetType() string {
	return nf.ext
}

func (nf *NFile) GetExport() []string {
	return nf.fds
}

func (nf *NFile) RunGenerate(nj *ninja.Ninja) error {
	return nil
}

type NObject struct {
	dir string
	fds []Node
	opt *Option
}

func (no *NObject) conv2obj(sfn string) string {
	bbb := filepath.Base(sfn)
	ext := filepath.Ext(bbb)
	bbb = strings.TrimSuffix(bbb, ext)
	return filepath.Join(no.dir, bbb+".o")
}

func (nf *NObject) GetType() string {
	return "object"
}

func (no *NObject) GetExport() []string {

	var rult []string

	for _, nf := range no.fds {
		/**/
		fps := nf.GetExport()

		/* replace ext */
		for i := 0; i < len(fps); i++ {
			/**/
			rult = append(rult, no.conv2obj(fps[i]))
		}
	}

	/**/
	return rult
}

func (no *NObject) RunGenerate(nj *ninja.Ninja) error {

	var incpath string = ""
	var ccopts string = ""

	/**/
	sary, err := no.opt.ExpectStrArray("cpppath", false)
	if err == nil {
		incpath = "-I" + strings.Join(sary, " -I")
	}

	/**/
	opts, err := no.opt.ExpectStrArray("ccopts", false)
	if err == nil {
		ccopts = strings.Join(opts, " ")
	}


	/**/
	for _, nf := range no.fds {

		err := nf.RunGenerate(nj)
		if err != nil {
			return err
		}

		/**/
		ext := nf.GetType()
		fps := nf.GetExport()

		for i := 0; i < len(fps); i++ {

			switch ext {
			case "c":
				bd := ninja.NewBuild([]string{no.conv2obj(fps[i])}, []string{fps[i]}, "cc")
				
				if len(incpath) > 0 {
					bd.AddVar("ccinc", incpath)
				}
				
				if len(ccopts) > 0 {
					bd.AddVar("ccopt", ccopts)
				}
				
				nj.AddBuild(bd)

			case "asm":
				bd := ninja.NewBuild([]string{no.conv2obj(fps[i])}, []string{fps[i]}, "as")
				
				if len(incpath) > 0 {
					bd.AddVar("asinc", incpath)
				}
				
				if len(ccopts) > 0 {
					bd.AddVar("asopt", ccopts)
				}
				
				nj.AddBuild(bd)

			default:
				return fmt.Errorf("object, ext not support, %s", ext)
			}

		}

	}

	return nil
}

type NArchive struct {
	dst string
	fds []Node
	opt *Option
}

func (na *NArchive) GetType() string {
	return "library"
}

func (np *NArchive) GetExport() []string {
	return []string{np.dst}
}

func (np *NArchive) RunGenerate(nj *ninja.Ninja) error {
	var ins []string

	for _, nf := range np.fds {

		err := nf.RunGenerate(nj)
		if err != nil {
			return err
		}

		/**/
		fps := nf.GetExport()
		ins = append(ins, fps...)
	}

	/**/
	bd := ninja.NewBuild([]string{np.dst}, ins, "ar")
	nj.AddBuild(bd)
	return nil
}

type NProgram struct {
	dst string
	fds []Node
	opt *Option
}

func (nf *NProgram) GetType() string {
	return "program"
}

func (np *NProgram) GetExport() []string {
	return []string{np.dst}
}

func (np *NProgram) RunGenerate(nj *ninja.Ninja) error {
	var ins []string

	for _, nf := range np.fds {

		err := nf.RunGenerate(nj)
		if err != nil {
			return err
		}

		/**/
		fps := nf.GetExport()
		ins = append(ins, fps...)
	}

	/**/
	bd := ninja.NewBuild([]string{np.dst}, ins, "lk")

	/**/
	sct, err := np.opt.ExpectString("script", false)
	if err == nil {
		bd.AddVar("sctarg", "--script="+sct)
	}

	/**/
	lbs, err := np.opt.ExpectStrArray("libs", false)
	if err == nil {
		bd.AddVar("libarg", strings.Join(lbs, " "))
	}

	nj.AddBuild(bd)
	return nil
}

type NBianry struct {
	dst string
	fds []Node
	opt *Option
}

func (nf *NBianry) GetType() string {
	return "binary"
}

func (np *NBianry) GetExport() []string {
	return []string{np.dst}
}

func (np *NBianry) RunGenerate(nj *ninja.Ninja) error {
	var ins []string

	for _, nf := range np.fds {

		err := nf.RunGenerate(nj)
		if err != nil {
			return err
		}

		/**/
		fps := nf.GetExport()
		ins = append(ins, fps...)
	}

	if len(ins) > 1 {
		return fmt.Errorf("input file must only-one")
	}

	/**/
	bd := ninja.NewBuild([]string{np.dst}, ins, "oc")
	nj.AddBuild(bd)
	return nil
}
